Skip to content

fix: shift signed CLR only for differential_abundance Wilcoxon - #472

Open
elhb wants to merge 1 commit into
PNA-3531from
PNA-3532
Open

elhb wants to merge 1 commit into
PNA-3531from
PNA-3532

Conversation

@elhb

@elhb elhb commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Makes differential_abundance valid on signed CLR.

difference is still mean(target) - mean(reference) on the original
matrix, matching PixelatorR RunDAA (fc.name = "difference"). Wilcoxon
runs on a test-only copy where each marker is shifted so its minimum is 0.
That shift is the same for every cell, so ranks (and thus p-values) match a
Wilcoxon on the original CLR. Scanpy still computes log-fold changes
internally, which break on negatives; we do not use those values.

Based on PNA-3531.

Fixes: PNA-3532

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update

How Has This Been Tested?

uv run pytest tests/pna/analysis/test_differential_abundance.py
  • test_differential_abundance_returns_expected_columns
  • test_differential_abundance_layer_from_obsm
  • test_differential_abundance_group_vars_splits_rows
  • test_differential_abundance_invalid_reference_raises
  • test_differential_abundance_signed_clr_keeps_original_difference_sign

Tiny "Offline" R vs Python sanity check

Frozen 5-cell PNA PBMC .pxl, 20 copies per cell with lognormal count noise, 50 treated vs 50 control (stratified by source cell). Planted count fold-changes, then pixelator CLR. Same CLR matrix in Python differential_abundance and pixelatorR RunDAA (Bonferroni).

Marker Role difference p_adj Python p_adj R
CD45 up ×2.4 0.940 0.0015 0.0015
HLA-DR-DP-DQ down ×0.45 −0.577 0.0020 0.0021
CD3e up ×2.6 0.731 0.0028 0.0028
CD16 down ×0.40 −0.459 0.067 0.068
CD5 up ×2.1 0.627 0.083 0.084
B2M / CD18 / mIgG1 held constant ≤0.076 1.0 1.0
  • Spearman(mean difference) = 1.000 (identical values)
  • Spearman(p) = 0.9999; sign of difference agrees for every marker
  • Top 5 hits in both tools = the 5 planted markers; no constant marker is significant
  • Default Bonferroni calls the three strongest planted markers; CD16 and CD5 are the next two (BH q = 0.017)

Wilcoxon p-values are not bit-identical (scanpy vs Seurat). Effect size is the same mean difference on the original CLR.

PR checklist:

  • This comment contains a description of changes (with reason).
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • If a new tool or package is included, I have updated dependencies in pyproject.toml and cited it properly
  • I have checked my code and documentation and corrected any misspellings
  • I have documented any significant changes to the code in CHANGELOG.md

Note

Medium Risk
Changes the Wilcoxon input path for signed CLR abundance matrices in a core analysis helper, though rank-preserving shifts and added tests reduce regression risk.

Overview
differential_abundance now supports signed CLR (matrices with negative values) without changing how effect sizes are reported.

When negatives are present, Wilcoxon runs on a test-only copy: each marker is shifted by its negative minimum so values are non-negative, using the same offset for every cell so ranks (and p-values) stay equivalent to Wilcoxon on the original CLR. difference, pct_1, and pct_2 are still computed on the original matrix, matching pixelatorR RunDAA mean-difference behavior. The previous warning plus log2 warning suppression is replaced by an info log and a dedicated _shift_markers_to_nonnegative helper; CHANGELOG documents the behavior change.

Tests now assert the input matrix is untouched, difference matches the planted group means, and p-values are finite on signed CLR.

Reviewed by Cursor Bugbot for commit ba89e99. Bugbot is set up for automated code reviews on this repo. Configure here.

@elhb
elhb force-pushed the PNA-3532 branch 3 times, most recently from 71a7191 to 3a3b424 Compare September 24, 2026 11:32
Keep mean difference on the original matrix, like PixelatorR RunDAA.

Co-authored-by: Cursor <cursoragent@cursor.com>
@elhb
elhb added this pull request to stack #473 September 25, 2026 11:44

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant